/**
 * Copyright (c) 2008, George Taylor
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted 
 * provided that the following conditions are met:
 * 
 *   * Redistributions of source code must retain the above copyright notice, this list of 
 *     conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright notice, this list of 
 *     conditions and the following disclaimer in the documentation and/or other materials 
 *     provided with the distribution.
 *   * Neither the name of the copyright holder's organization nor the names of its contributors 
 *     may be used to endorse or promote products derived from this software without specific 
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package uk.co.taylorconsulting.annoweb.web.builder;

import java.io.File;
import java.io.FileOutputStream;
import java.util.HashMap;

import org.apache.log4j.Logger;

import uk.co.taylorconsulting.annoweb.annotation.security.SecurityProcessor;
import uk.co.taylorconsulting.annoweb.web.builder.data.ActionHolder;
import uk.co.taylorconsulting.annoweb.web.builder.data.Site;

public class SecurityHandlerBuilder extends SiteBuilder {
	private static final Logger                 LOG      = Logger.getLogger(SecurityHandlerBuilder.class);
	private static final SecurityHandlerBuilder instance = new SecurityHandlerBuilder();
	
	public static void buildSite(HashMap<String, Site> sites, File tmpLocation) {
		instance.buildSite(sites, tmpLocation, true);
	}
	
	public void buildSite(HashMap<String, Site> sites, File tmpLocation, boolean ok) {
		File siteFile = new File(tmpLocation, "SecurityHandler.java");
		StringBuffer str = addClassStart(sites);
		str.append("}\n");
		FileOutputStream fos;
		try {
			fos = new FileOutputStream(siteFile);
			fos.write(str.toString().getBytes());
			fos.close();
		} catch (Exception e) {
			LOG.warn("Error writing class to " + siteFile.getAbsolutePath() + "(" + e.getMessage() + ")");
		}
	}
	
	private StringBuffer addClassStart(HashMap<String, Site> sites) {
		Site s = sites.get("xxDefaultxx");
		StringBuffer str = new StringBuffer("import javax.servlet.http.*;\n");
		str.append("import javax.servlet.*;\n");
		str.append("import java.util.*;\n");
		str.append("import org.apache.log4j.Logger;\n");
		str.append("public class SecurityHandler extends uk.co.taylorconsulting.web.controller.WebHelper {\n");
		str.append("\tprivate static final Logger LOG = Logger.getLogger(SecurityHandler.class);\n");
		str.append("\tprivate static final ").append("SecurityHandler instance = new ").append("SecurityHandler").append("();\n");
		str.append("\tprivate ").append("SecurityHandler(){}\n");
		str.append("\tpublic static SecurityHandler getInstance() {\n");
		str.append("\t\treturn instance;\n");
		str.append("\t}\n");
		str.append("\tpublic static boolean checkSecurity(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, String... roles) {\n");
		str.append("\t\treturn instance.checkSecurity(request, response, servletContext, true, roles);\n");
		str.append("\t}\n");
		str.append("\tpublic boolean checkSecurity(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, boolean ok, String... roles) {\n");
		HashMap<String, HashMap<String, Class<? extends SecurityProcessor>>> security = s.getGlobals().getSecurity();
		str.append("\t\tString context = request.getServletPath().toLowerCase();\n");
		str.append("\t\tif (context.lastIndexOf(\"\\\\\") > 1) {\n");
		str.append("\t\t\tcontext = context.substring(0,context.indexOf(\"\\\\\"));\n");
		str.append("\t\t} else {\n");
		str.append("\t\t\tcontext=null;\n");
		str.append("\t\t}\n");
		str.append("\t\tString realm=request.getServerName();\n");
		boolean first = true;
		boolean moreThanOne = false;
		for (String realm : security.keySet()) {
			if (!realm.equalsIgnoreCase("all")) {
				HashMap<String, Class<? extends SecurityProcessor>> contexts = security.get(realm);
				if (first) {
					first = false;
					str.append("\t\tif (realm.equalsIgnoreCase(\"");
				} else {
					str.append("\t\t} else if (realm.equalsIgnoreCase(\"");
				}
				str.append(realm).append("\")) {\n");
				moreThanOne = addContexts(str, contexts, moreThanOne);
				str.append("\t\t}\n");
			}
		}
		if (security.containsKey("all")) {
			HashMap<String, Class<? extends SecurityProcessor>> contexts = security.get("all");
			if (!first) {
				str.append("\t\t} else {\n");
			}
			moreThanOne = addContexts(str, contexts, moreThanOne);
			if (!first) {
				str.append("\t\t}\n");
			}
		}
		if (moreThanOne) {
			str.append("\t\treturn false;\n");
		}
		str.append("\t}\n");
		str.append("\tpublic void login(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {\n");
		ActionHolder login = s.getGlobals().getLogin();
		ActionHolder notAuthed = s.getGlobals().getNotAuthed();
		if (login != null) {
			addForward(str, null, login.getMethod().getDeclaringClass(), login.getMethod().getName(), false, "\t\t", s, null);
		}
		str.append("\t}\n");
		str.append("\tpublic void notAuthed(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {\n");
		if (notAuthed != null) {
			addForward(str, null, notAuthed.getMethod().getClass(), notAuthed.getMethod().getName(), false, "\t\t", s, null);
		}
		str.append("\t}\n");
		return str;
	}
	
	private boolean addContexts(StringBuffer str, HashMap<String, Class<? extends SecurityProcessor>> contexts, boolean moreThanOne) {
		boolean first2 = true;
		for (String context : contexts.keySet()) {
			if (context.equals("")) {
				continue;
			}
			if (first2) {
				first2 = false;
				str.append("\t\t\tif (context.equalsIgnoreCase(\"");
			} else {
				str.append("\t\t\t} else if (context.equalsIgnoreCase(\"");
			}
			str.append(context).append("\")) {\n");
			addContext(str, contexts, context);
		}
		if (!first2) {
			str.append("\t\t\t}\n");
			moreThanOne = true;
		}
		if (contexts.containsKey("")) {
			addContext(str, contexts, "");
		}
		return moreThanOne;
	}
	
	private void addContext(StringBuffer str, HashMap<String, Class<? extends SecurityProcessor>> contexts, String context) {
		str.append("\t\t\t\tuk.co.taylorconsulting.annotation.security.SecurityProcessor sec = (").append(contexts.get(context).getCanonicalName()).append(")getCache(\"").append(contexts.get(context).getCanonicalName()).append("\");\n");
		str.append("\t\t\t\tfor (String role:roles) {\n");
		str.append("\t\t\t\t\tuk.co.taylorconsulting.annotation.security.SecurityOptions resp = sec.isRoleValid(role,request);\n");
		str.append("\t\t\t\t\tswitch(resp) {\n");
		str.append("\t\t\t\t\t\tcase login: {\n");
		str.append("\t\t\t\t\t\t\tlogin(request, response, servletContext);\n");
		str.append("\t\t\t\t\t\t\treturn false;\n");
		str.append("\t\t\t\t\t\t}\n");
		str.append("\t\t\t\t\t\tcase notAuthorised: {\n");
		str.append("\t\t\t\t\t\t\tnotAuthed(request, response, servletContext);\n");
		str.append("\t\t\t\t\t\t\treturn false;\n");
		str.append("\t\t\t\t\t\t}\n");
		str.append("\t\t\t\t\t}\n");
		str.append("\t\t\t\t}\n");
		str.append("\t\t\t\treturn true;\n");
	}
}
